/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package cz.dusa.switco;

import java.util.ArrayList;
import java.util.List;

/**
 * Class holding a collection of tasks and implementing methods calculating
 * information about these tasks.
 * @author vdusa
 */
public final class TasksHolder {
    private ConvexCostFunctionSum convexCostFunctionSum = null;
    private List<Task> tasks = new ArrayList<Task>();

    public TasksHolder(){}

    /**
     * Creates TasksHolder object with given tasks
     * @param tasks to be added to the Tasks object
     */
    public TasksHolder(List<Task> tasks){
        addAll(tasks);
    }

    /**
     * @return Minimal tree shift over all tasks
     */
    public Double getMinTreeShift(){
        Double minTreeShift = Double.NaN;
        EqualityTree tree = null;
        if(getTasks().size() > 0){
            tree = getTasks().get(0).getEqualityTree();
        }

        for(Task task: getTasks()){
            assert tree.equals(task.getEqualityTree()) : "Tasks contain tasks from different trees!";
            if (minTreeShift.equals(Double.NaN) || minTreeShift>task.getTreeShift()){
                minTreeShift = task.getTreeShift();
            }
        }

        return minTreeShift;
    }

    /**
     * @return Minimal tree shift over all tasks
     */
    public Double getMinStart(){
        Double minStart = Double.NaN;
        EqualityTree tree = null;
        if(getTasks().size() > 0){
            tree = getTasks().get(0).getEqualityTree();
        }
        for(Task task: getTasks()){
            assert tree.equals(task.getEqualityTree()) : "Tasks contain tasks from different trees!";
            if (minStart.equals(Double.NaN) || minStart>task.getStart()){
                minStart = task.getStart();
            }
        }

        return minStart;
    }

    /**
     * Adds all tasks from the given list
     * @param tasks to be added
     */
    public void addAll(List<Task> tasks){
        for(Task task: tasks){
            add(task);
        }
        setConvexCostFunctionSum(null);
    }
    /**
     * Adds a task
     * @param task to be added
     */
    public void add(Task task){
        tasks.add(task);
        setConvexCostFunctionSum(null);
    }

    /**
     * Removes the given task 
     * @param task 
     */
    public void remove(Task task){
        tasks.remove(task);
        setConvexCostFunctionSum(null);
    }

    /**
     * Clears all tasks
     */
    public void clear(){
        tasks.clear();
    }
    /**
     *
     * @return tasks as a list
     */
    public List<Task> getTasks(){
        return tasks;
    }
    /**
     * @return the convexCostFunctionSum
     */
    public ConvexCostFunctionSum getConvexCostFunctionSum() {
        if (convexCostFunctionSum == null){
            setConvexCostFunctionSum(new ConvexCostFunctionSum(getTasks()));
        }

        return convexCostFunctionSum;
    }

    /**
     * @param convexCostFunctionSum the convexCostFunctionSum to set
     */
    public void setConvexCostFunctionSum(ConvexCostFunctionSum convexCostFunctionSum) {
        this.convexCostFunctionSum = convexCostFunctionSum;
    }

    /**
     * @return true if the convex cost function sum is null. (Only for testing purpose)
     */
    public boolean isConvexCostFunctionSumNull(){
        return convexCostFunctionSum == null;
    }

    /**
     * @return optimal start time window for the current collection of tasks
     * For more information see {@link ConvexCostFunctionSum#getArgMin()}
     */
    public Double[] getOptimalStartTimeWindow(){
        Double[] result = getConvexCostFunctionSum().getArgMin();

        /* the function getConvexCostFunctionSum().getArgMin() has calculated the optimum according
         * to the current tree shift of the tasks. But now we can have only a part of tasks from some tree
         * (not the whole tree) - in this case wouldn't be the result correct. Thus we have to correct it
         * by current getMinTreeShift over the part of tasks from the tree. If we have whole tree, the 
         * getMinTreeShift()=0 and everything is ok.
         */
        
        result[0] += getMinTreeShift();
        result[1] += getMinTreeShift();
        
        return result;
    }

    /**
     * @return true if the current tasks are strictly early
     */
    public boolean isStrictlyEarly(){
        return getMinStart() < getOptimalStartTimeWindow()[0];
    }

    /**
     * @return true if the current tasks are early
     */    
    public boolean isEarly(){
        return getMinStart() <= getOptimalStartTimeWindow()[1];
    }

    /**
     * @return true if the current tasks are on time (i.e. they are early and late together)
     */    
    public boolean isOnTime(){
        return isEarly() && isLate();
    }

    /**
     * @return true if the current tasks are late
     */    
    public boolean isLate(){
        return getOptimalStartTimeWindow()[0] <= getMinStart();
    }
    
    /**
     * @return true if the current tasks are strictly strictly late
     */
    public boolean isStrictlyLate(){
        return getOptimalStartTimeWindow()[1] < getMinStart();
    }

    boolean contains(Task task) {
        return tasks.contains(task);
    }
}
